蓝牙遥控器连接流程分析 您所在的位置:网站首页 蓝牙 遥控器 蓝牙遥控器连接流程分析

蓝牙遥控器连接流程分析

2023-08-04 11:31| 来源: 网络整理| 查看: 265

背景

最近在一个Linux系统的ARM板子上移植一款蓝牙芯片,因为我们做的是机顶盒,所以首要功能就是能连接上蓝牙遥控器,并且能正常的接收按键。之前在安卓平台,连接上蓝牙遥控器后,会自动创建/dev/input/eventX和/dev/hidrawX节点,通过读取这两个节点,能看到我们机顶盒接收到的按键数据。但是最近在Linux平台,连接上蓝牙遥控器后,并没有创建什么节点,所以我也不知道怎么将遥控器数据上抛给上层应用去读取。网上尝试找一些资料,不过这方面的文章比较少,所以决定自己加些打印,跟一下代码流程,下面的文章记录一下我的跟踪思路。

正文 一、bus、driver、device总线部分

在正式开始分析代码前,我们先了解一个概念:match函数,一个由具体的bus driver实现的回调函数。当任何属于该Bus的device或者device_driver添加到内核时,内核都会调用该接口,如果新加的device或device_driver匹配上了自己的另一半的话,该接口要返回非零值,此时Bus模块的核心逻辑就会执行后续的处理。

0、在调用probe函数之前,会先调用match函数

代码目录:drivers\hid\hid-core.c 函数调用关系: hid_bus_match ->hid_match_device     ->hid_match_one_id

具体看一下hid_match_one_id函数,会通过判断vendor和product等值,来确定是否匹配成功,这两个值就是连接的蓝牙设备传过来的。

static bool hid_match_one_id(struct hid_device *hdev, const struct hid_device_id *id) { return (id->bus == HID_BUS_ANY || id->bus == hdev->bus) && (id->group == HID_GROUP_ANY || id->group == hdev->group) && (id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) && (id->product == HID_ANY_ID || id->product == hdev->product); }

1、当调用到probe函数,会有下面的所有流程发生

代码目录: 1)hid_device_probe:drivers\hid\hid-core.c 2)hid_hw_start:include\linux\hid.h 函数调用关系: hid_device_probe ->hid_hw_start ->hid_connect

 

2、上面一步可以看出来最后调用到connect函数,字面意思就是开始正式连接,这个函数比较重要,我们进到具体代码看一下

代码目录:drivers\hid\hid-core.c int hid_connect(struct hid_device *hdev, unsigned int connect_mask) { ... /* 下面会创建/dev/input/eventX节点 */ if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev, connect_mask & HID_CONNECT_HIDINPUT_FORCE)) hdev->claimed |= HID_CLAIMED_INPUT; /*下面会创建/dev/hidrawX节点*/ if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev)) hdev->claimed |= HID_CLAIMED_HIDRAW; ... hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n", buf, bus, hdev->version >> 8, hdev->version & 0xff, type, hdev->name, hdev->phys); return 0; }

在connect函数中有两个比较重要的函数调用hidinput_connect和hidraw_connect,下面我们分别看一下:

2.1、

代码目录: 1)hidinput_connect:drivers\hid\hid-core.c 2)input_register_device:common\drivers\input\input.c: 函数调用关系: hidinput_connect ->input_register_device //input_register_device就是我们熟悉的,将设备注册到Input子系统 int input_register_device(struct input_dev *dev) { ... path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device",path ? path : "N/A"); kfree(path); ... error = device_add(&dev->dev); if (error) goto err_free_vals; ... }

上面的代码我们又遇到一个很关键的函数:device_add(),下面继续跟踪到device_add函数里面去。

2.1.1、

代码目录: 1)device_add:drivers\base\core.c 2)bus_probe_device:drivers\base\bus.c 3)device_attach:drivers\base\dd.c 4)__device_attach:drivers\base\dd.c 5)driver_match_device:drivers\base\base.h 函数调用关系: device_add ->bus_probe_device ->device_attach ->__device_attach ->driver_match_device //最终会调用driver的match函数

在设备指定总线,且允许自动匹配的前提下(可以通过节点查看:cat /sys/bus/hid/drivers_autoprobe),bus_probe_device调用device_attach(dev),而在device_attach中又分两个分支: 第一、设备指定了驱动,那么device_attach直接调用device_bind_driver(dev)将驱动和设备绑定完事。 第二、设备没有指定驱动,那么device_attach通过bus_for_each_drv(dev->bus, NULL, dev, __device_attach)枚举总线上的驱动与设备进行匹配 具体代码如下所示:

int device_attach(struct device *dev) { int ret = 0; device_lock(dev); if (dev->driver) { /*指定了驱动*/ if (klist_node_attached(&dev->p->knode_driver)) { ret = 1; goto out_unlock; } ret = device_bind_driver(dev); if (ret == 0) ret = 1; else { dev->driver = NULL; ret = 0; } } else { /* 通过枚举总线上的驱动和驱动进行匹配 */ ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); pm_request_idle(dev); } out_unlock: device_unlock(dev); return ret; }

2.2、

代码目录: hidraw_connect:drivers\hid\hidraw.c 函数调用关系: hidraw_connect ->device_create int hidraw_connect(struct hid_device *hid) { ... dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),NULL, "%s%d", "hidraw", minor); ... }

我们都知道,device_create()函数就是/dev下创建节点的,所以调用完hidraw_connect()后,就会有/dev/hidrawX节点了

 

3、

到目前为止,我们已经知道设备是怎么通过总线和驱动对应上了,但是问题又来了,怎么才能调用到.match函数呢?这个问题我们接下来分析

二、uhid驱动部分

通过加日志和搜索大量的代码,终于找到了怎么才能调用到上面的.match函数——hid_bus_match。下面我们看一下内核中的uhid驱动代码。

1、

代码目录: drivers\hid\uhid.c 函数调用关系: uhid_char_write ->uhid_dev_create ->hid_add_device ->device_add static const struct file_operations uhid_fops = { .owner = THIS_MODULE, .open = uhid_char_open, .release = uhid_char_release, .read = uhid_char_read, .write = uhid_char_write, .poll = uhid_char_poll, .llseek = no_llseek, }; static struct miscdevice uhid_misc = { .fops = &uhid_fops, .minor = UHID_MINOR, .name = UHID_NAME, }; static int __init uhid_init(void) { return misc_register(&uhid_misc); }

从函数调用关系来看,又来到我们上面讲到的device_add函数了,就是在bus总线上匹配对应的驱动。翻看uhid这个驱动的源代码,我们可以发现这是一个misc杂散设备,实际上也确实创建了一个/dev/uhid节点。所以回到我们文章最开始的问题,怎么创建到/dev/input/eventX和/dev/hidrawX节点?流程大概应该是这样:  

int fd = open(/dev/uhid); write(fd, ...);

最终就会调用到uhid_char_write,并且最终匹配到对应的驱动程序,并且过程中创建了/dev/input/eventX和/dev/hidrawX节点。有了这个思路,我们自然就去查找,到底谁负责打开这个节点呢?自然而然我们就会想到是蓝牙协议栈做这个工作了,搜索代码后,果然是这样。我搜索了mtk的蓝牙协议栈代码,就发现了有打开/dev/uhid的动作。  

2、

这里再提一下uhid设备匹配到的驱动程序——hid-generic。

代码目录: drivers\hid\hid-generic.c static const struct hid_device_id hid_table[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, HID_ANY_ID, HID_ANY_ID) }, { } }; MODULE_DEVICE_TABLE(hid, hid_table); static struct hid_driver hid_generic = { .name = "hid-generic", .id_table = hid_table, }; module_hid_driver(hid_generic);

注册完uhid设备后,会遍历所有的HID的驱动,因为从hid-generic的id_table来看是匹配任何设备的,所以最后就匹配到了hid-generic。不过大概看了一下这个驱动,好像并没有实际做什么。

结语

到目前为止就简单的跟踪了一下流程,还有很多细节没有细究,有兴趣的同学可以分析的更详细,但是有了上面的流程指引,我相信分析起来也会顺利很多了。另外,上面的代码流程是基于Linux 3.14.29版本,对于我现在准备做的Linux 4.9.113会有稍许不同,但是基本流程类似。  



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有